home *** CD-ROM | disk | FTP | other *** search
- The Rise And Fall of TObject - update for Delphi 4:
-
- The new Delphi 4 version changes things a bit when it comes to how
- constructors and destructors work.
-
-
- NEW VIRTUAL METHODS
- There are two new virtual methods defined in TObject that are
- called during construction and destruction:
-
- TObject = class
- ..
- procedure AfterConstruction; virtual;
- procedure BeforeDestruction; virtual;
- ..
- end;
-
- By default these methods do nothing and are implemeted in TObject as:
-
- procedure TObject.AfterConstruction;
- begin
- end;
-
- procedure TObject.BeforeDestruction;
- begin
- end;
-
- These are normal methods, so no special code is generated here.
-
- The new code generatead for the constructor, now looks like this:
-
- class function TMyObject.Create(SelfClass: TClass; CalledWithClass: boolean): TMyObject;
- var
- Self: TMyObject;
- begin
- if CalledWithClass
- then Self := _ClassCreate(SelfClass)
- else Self := TObject(SelfClass);
-
- // Actual constructor code goes here
-
- if CalledWithClass then
- begin
- // Remove exception frame
- _AfterConstruction(Self);
- end;
- Result := Self;
- end;
-
- _AfterConstruction is a new magic function defined in the System unit.
- It's pseudo-code is something like this:
-
- procedure _AfterConstruction(Instance: TObject);
- begin
- Instance.AfterConstruction;
- end;
-
- Also the generated code for the destructors has been changed
- to handle the new BeforeDestruction method:
-
- procedure TObject.Destroy(Self: TObject; Deallocate: boolean);
- begin
- if Deallocate then
- _BeforeDestruction(Self, Deallocate);
- // Actual destructor code goes here
- if Deallocate then
- _ClassDestroy(Self);
- end;
-
- _BeforeDestruction is another new magic function defined in the System unit.
- It's pseudo-code is something like this:
-
- procedure _BeforeDestruction(Instance: TObject; Deallocate: boolean);
- begin
- if Deallocate then
- Instance.BeforeDestruction;
- end;
-
- The introduction of the AfterConstruction and BeforeDestruction methods allows us
- to hook into the construction chain at yet another level. The total picture of
- the construction process thus looks like this:
-
- constructor
- _CreateClass
- NewInstance
- GetMem
- InitInstance
- _AfterConstruction
- AfterConstruction
-
- destructor
- _DestroyClass
- FreeInstance
- CleanupInstance
- FreeMem
- _BeforeDestruction
- BeforeDestruction
-
- Starts to look a little complicated doesn't it?
-
- The reason for introducing these new virutal methods seems
- to be compability with the C++Builder version of the VCL and
- compiler. If you look closely at the VCL source code supplied
- with Delphi 4 Pro and up, you will find that these methods
- are only overriden in the TCustomForm and TDataModule classes.
-
-
- CHANGED CONSTRUCTOR SEMANTICS
- Another issue to help compability with BCB seems to be the
- value used for the implicit CalledWithClass parameter of
- constructors. In previous versions of Delphi, the parameter
- (contained in the DL register) would always be 1=true or 0=false.
-
- In Delphi 4, this parameter can also be -1 (or $FF when treated
- as a byte). The DL parameter will be -1 when calling a constructor
- through an existing object instance from outside a constructor.
-
- That sentence was a bit complicated so lets give a couple of examples:
-
- Ok, calling a constructor through a class reference, always sets the DL
- parameter to 1, no matter where it is called from:
-
- MyObject := TMyObject.Create;
-
- is compiled into:
-
- MyObject := TMyObject.Create(TMyObject, DL=1);
-
- This is the same as before. No change here.
- When calling a constructor through an existing object reference, things
- will work as before as long as you are calling the constructor from
- within another constructor:
-
- constructor TMyOtherObject.Create;
- begin
- inherited Create;
- ...
- end;
-
- constructor TMyOtherObject.Create2;
- begin
- Self.Create;
- ...
- end;
-
- This will compile into (we only consider the calls here):
-
- constructor TMyOtherObject.Create;
- begin
- inherited Create(Self, DL=0);
- ...
- end;
-
- constructor TMyOtherObject.Create2;
- begin
- Self.Create(Self, DL=0);
- ...
- end;
-
- So far so good. Things still work exactly has they have before.
- Now to the culprit. If we move the call to the constructor
- outside the constructor, DL will be set to -1 instead of 0.
-
- For instance:
-
- procedure TMyOtherObject.Init;
- begin
- Self.Create;
- ...
- end;
-
- This will compile into:
-
- procedure TMyOtherObject.Init;
- begin
- Self.Create(Self, DL=-1);;
- ...
- end;
-
- So now the second implied parameter to the constructor can have
- three values 0, 1 or -1. Here is how the constructor behaves in each case:
-
- Case 1 (DL=1):
- a) Called on class reference
- b) Setup an exception frame to automatically call free in case of problems
- c) Allocate and initialize memory for the instance
-
- Case 2 (DL=0):
- a) Called on existing instance from another constructor
- b) Do not setup any exception frame
- c) Do not allocate memory for the instance
-
- Case 3 (DL=-1):
- a) Called on existing instance from non-constructor code
- b) Setup an exception frame to automatically call free in case of problems
- c) Do not allocate memory for the instance
-
- Case 1 and 2 works like before. Case 3 is the new situation and it is point 3b) that
- can potentially case problems. This is a change in the semantics of constructors.
-
- The problem is that earlier, calling a constructor on an existing object instance,
- the constructor would work just like a method call. With the new semantics,
- the instance will be freed if the constructor raises an exception.
-
- So what should you do to avoid this problem? As far as possible, refrain from using
- the construct:
-
- Instance.Create;
-
- from other places than inside other constructors.
-
-